/**
 * Copyright (c) 2011, Yahoo! Inc. All rights reserved.
 * Code licensed under the BSD License:
 * https://github.com/yui/yuidoc/blob/master/LICENSE
 */
'use strict';

var path = require('path');
var express = require('express');

YUI.add('server', function (Y) {

    /**
     * Provides the `--server` server option for YUIDoc
     * @class Server
     * @module yuidoc
     */
    var Server = {
        /**
         * Cache for external mixed in data.
         * @property _externalData
         * @private
         * @type Object
         */
        _externalData: null,
        /**
         * Middleware to parse the API docs per request
         * @method parse
         * @param {Request} req Express request object
         * @param {Response} res Express response object
         * @param {Function} next Express next callback
         */
        parse: function (req, res, next) {
            var json = (new Y.YUIDoc(Server.options)).run();
            Server.options = Y.Project.mix(json, Server.options);
            Server.builder = new Y.DocBuilder(Server.options, json);
            if (Server._externalData) {
                Server.options.externalData = Server._externalData;
                Server.builder._mixExternal();
            }

            next();
        },
        /**
         * Create the routes used to serve YUIDoc files dynamically
         * @method routes
         */
        routes: function () {
            var app = Server.app;

            app.get('/', Server.parse, function (req, res) {
                Server.home(req, res);
            });
            app.get('/api.js', function (req, res) {
                Server.builder.renderAPIMeta(function (js) {
                    res.contentType('.js');
                    res.send(js);
                });
            });


            app.get('/classes/:class.html', Server.parse, function (req, res, next) {
                Server.clazz(req, res, next);
            });

            app.get('/modules/:module.html', Server.parse, function (req, res, next) {
                Server.module(req, res, next);
            });

            app.get('/files/:file.html', Server.parse, function (req, res, next) {
                Server.files(req, res, next);
            });

            //These routes are special catch routes..

            app.get('//api.js', function (req, res) {
                res.redirect('/api.js');
            });
            app.get('//classes/:class.html', Server.parse, function (req, res, next) {
                Server.clazz(req, res, next);
            });

            app.get('//modules/:module.html', Server.parse, function (req, res, next) {
                Server.module(req, res, next);
            });

            app.get('//files/:file.html', Server.parse, function (req, res, next) {
                Server.files(req, res, next);
            });

            app.get('*', function (req, res) {
                var type = req.url.split('/')[1],
                    html = ['<h1>Item Not Found in internal meta-data</h1>'];

                if (type === 'class') {
                    type = 'classes';
                }
                if (Server.builder && Server.builder.data && Server.builder.data[type]) {
                    if (Object.keys(Server.builder.data[type]).length) {
                        html.push('<p>But I know about these? Misname your module?</p>');
                        html.push('<ul>');
                        Object.keys(Server.builder.data[type]).forEach(function (item) {
                            html.push('<li><a href="../' + path.dirname(req.url) + '/' + item + '.html">' + item + '</a></li>');
                        });
                        html.push('</ul>');
                    }
                }


                res.status(404).send(html.join('\n'));
            });

        },
        /**
         * `/files` endpoint
         * @method files
         * @param {Request} req Express request object
         * @param {Response} res Express response object
         */
        files: function (req, res, next) {
            var fileName = req.params.file,
                data;
            Object.keys(Server.builder.data.files).forEach(function (file) {
                if (fileName === Server.builder.filterFileName(file)) {
                    data = Server.builder.data.files[file];
                }
            });

            if (!data) {
                return next();
            }

            Y.log('Serving /files/' + data.name, 'info', 'server');


            Server.builder.renderFile(function (html) {
                res.send(html);
            }, data, (req.xhr ? 'xhr' : 'main'));
        },
        /**
         * `/classes` endpoint
         * @method clazz
         * @param {Request} req Express request object
         * @param {Response} res Express response object
         */
        clazz: function (req, res, next) {
            var className = req.params.class;
            Y.log('Serving /classes/' + className + '.html', 'info', 'server');
            if (!Server.builder.data.classes[className]) {
                return next();
            }
            Server.builder.renderClass(function (html) {
                res.send(html);
            }, Server.builder.data.classes[className], (req.xhr ? 'xhr' : 'main'));
        },
        /**
         * `/modules` endpoint
         * @method modules
         * @param {Request} req Express request object
         * @param {Response} res Express response object
         */
        module: function (req, res, next) {
            var modName = req.params.module;
            Y.log('Serving /modules/' + modName + '.html', 'info', 'server');
            if (!Server.builder.data.modules[modName]) {
                return next();
            }
            Server.builder.renderModule(function (html) {
                res.send(html);
            }, Server.builder.data.modules[modName], (req.xhr ? 'xhr' : 'main'));
        },
        /**
         * `/` endpoint
         * @method home
         * @param {Request} req Express request object
         * @param {Response} res Express response object
         */
        home: function (req, res) {
            Y.log('Serving index.html', 'info', 'server');
            Server.builder.renderIndex(function (html) {
                res.send(html);
            });
        },
        /**
         * Creates the Express server and prep's YUI for serving
         * @method init
         */
        init: function () {
            var stat;

            Server.app = express();
            //console.log(Server.options);
            stat = Server.options.themedir || path.join(__dirname, '../', 'themes', 'default');
            Server.app.use(express.static(stat));
            Server.routes();
            Server.app.listen(Server.options.port);

            Y.log('Starting server: http:/' + '/127.0.0.1:' + Server.options.port, 'info', 'server');
        },
        /**
         * Start the server with the supplied options.
         * @method start
         * @param {Object} options Server options
         */
        start: function (options) {
            options = Y.Project.init(options);
            Server.options = options;

            Server.options.cacheTemplates = false; //Don't cache the Handlebars templates
            Server.options.writeJSON = false; //Don't write the JSON file out

            Y.config.logExclude.yuidoc = true;
            Y.config.logExclude.docparser = true;
            Y.config.logExclude.builder = true;

            if (Server.options.external) {
                Y.log('Fetching external data, this may take a minute', 'warn', 'server');
                var json, builder;

                json = (new Y.YUIDoc(Server.options)).run();
                Server.options = Y.Project.mix(json, Server.options);

                builder = new Y.DocBuilder(Server.options, json);
                builder.mixExternal(function () {
                    Y.log('External data fetched, launching server..', 'info', 'server');
                    Server._externalData = builder.options.externalData;
                    Server.init();
                });

            } else {
                Server.init();
            }
        }
    };

    Y.Server = Server;
});
